import re

import arrow
import json

from io import BytesIO
from flask import request, jsonify, g, current_app as app
from flask_jwt_extended.utils import get_jwt_identity
from bson.objectid import ObjectId

from app.config.settings import DEBUG, NETWORK_SCAN_PROFILE
from app.handler.jobs import JobDownload
from app.libs import scanner
from app.libs.oms import Client
from app.libs.excel import Excel
from app.libs.redprint import RedPrint
from app.libs.regexp import NAME_RE
from app.libs.utils import create_job, enable_monitor, \
    schedule_asset, schedule_http, schedule_ping, schedule_security_event, schedule_setting, \
    schedule_ssl, schedule_vul, schedule_content, handle_custom_urls_data, check_job_settings_interval, \
    check_site_domain_enable_used, check_http_max_task_enable_used, check_ping_max_task_enable_used, \
    check_site_port_trait_used, check_job_balance, get_active_user_info, get_verified_job_list, get_balance_maps, \
    get_asset_maps, schedule_host_vul, enable_web_sec_task, get_prelogin_page, enable_available_task, enable_task_group, \
    balance_task_count_sync
from app.libs.utility import get_request_args
from app.libs.scanner import GetAllProxyIPs
from app.handler.v2_index import SumWarnings, LastTask
from app.handler.v2_jobs import JobHelper, JobVerifyHelper, get_available_task_log_detail
from app.handler.v2_users import UserHelper
from app.handler.v2_balances import BalancesOne
from app.auth import permission_required
from app.db.models.users import User
from app.db.models.jobs import Job, Asset as JobAsset, CustomMonitorPage
from app.db.models.tasks import Task, TaskSpiderUrl, ChangeCheckBasepage
from app.db.models.asset_tasks import AssetTasks, WsTaskTime
from app.db.models.packages import Packages
from app.db.models.balances import Balances
from app.db.models.batch_import import Asset, BatchImportAsset
from app.validators.job import (
    AddJobForm, AssetSettingForm, CustomVulSettingsForm, JobBatchVerification,
    JobCydEnableForm, HttpSettingsForm,
    PingSettingsForm, BatchRemoveTaskForm, BatchRemoveScannerTaskForm, BatchRemoveTaskSessionForm,
    SecurityEventSettingsForm,
    SpecialVulSettingsForm, SslSettingForm, CustomAssetSettingForm, CustomSslSettingForm,
    CustomSecurityEventSettingsForm, RescanTaskForm, VulSettingsForm, BatchStopTaskForm,
    BatchStopTaskSessionForm,
    ContentSettingsForm, CustomContentSettingsForm, JobDownloadForm, JobBatchMonitorOrAlertSwitchForm,
    HostVulSettingForm, HostVulTask, HostVulTaskDelete, HostVulTaskList, TaskSummary, HostVulTaskSwitch,
    HostVulTaskSettings, JobIdForm, AdminHostVulTaskList, JobCount,
    CustomIpv6SettingForm, CustomIPv6AssetTaskForm,
    WebSecurityTaskAddForm, WebSecurityManualTaskAddForm, WebSecurityTaskUpdateForm,
    WebSecurityBatchForm, WebSecurityBatchSwitchForm, AvailableTaskAddForm, AvailableTaskUpdateForm,
    AvailableBatchForm, AvailableBatchSwitchForm, UpdateTaskGroupConfigForm, EnableProxyIpForm
)
from app.errors import (
    EmptyData, ExistBatchImportAsset,
    ParameterError, PermissionDenied,
    Success, UploadFileError,
    ServerError, CustomNameError
)
from app.celery.batch_import_asset import do_batch_import_asset
from app.celery.batch_update_task_config import batch_update_task_config
from app.api.v2.common import param_input
from app.log.logs import UJobLog, UAssetTaskLog, DAssetTaskLog, CAssetTaskLog
from app.api.v2 import job_util
from app.libs.enums import (
    TaskType,
    TASK_TYPE_TUPLE,
    TaskTriggerType,
    TaskStatus,
    MONITOR_TYPE_TUPLE,
    TASK_TYPE_CONFIG,
    WS_TASK_GROUP_TUPLE
)
from app.api.v2.asset_util import switch_job_tasks

import logging


logger = logging.getLogger('app')

api = RedPrint('jobs')


# TODO 废弃
@api.route('', methods=['POST'])
def add_job():
    form = AddJobForm().validate_for_api()
    job, asset = create_job(
        uid=form.uid.data,
        target_url=form.targetUrl.data,
        note=form.note.data,
        source_ip=form.sourceIp.data,
        enable_monitor=form.enableMonitor.data,
        enable_alert=form.enableAlert.data,
        import_type='')

    if job.enableMonitor and job.verification.isVerified:
        enable_monitor(job, asset, form.immediateExec.data)
    return Success()


# TODO 废弃
@api.route('/http/schedule', methods=['POST'])
def http(data=None):
    """对http监测任务保存,会对任务进行判断是新增/更新/还是删除

    Returns:
        [type]: [description]
    """
    form = HttpSettingsForm(data).validate_for_api()
    if form.is_changed():
        job = form.job
        job.enableMonitor = form.enableMonitor.data
        job.sourceIp = form.sourceIp.data
        custom_urls_data = handle_custom_urls_data(form.customMonitorPage.data)
        schedule_http(
            job,
            form.settings,
            immediate_exec=form.immediateExec.data,
            custom_monitor_urls=custom_urls_data
        )
        job.httpSettings = form.settings
        job.customMonitorPage = CustomMonitorPage.from_dict(custom_urls_data)
        job.save()
    return Success()


# TODO 废弃
@api.route('/ping/schedule', methods=['POST'])
def ping(data=None):
    """对ping监测任务保存,会对任务进行判断是新增/更新/还是删除

    Returns:
        [type]: [description]
    """

    form = PingSettingsForm(data).validate_for_api()
    if form.is_changed():
        job = form.job
        job.enableMonitor = form.enableMonitor.data
        job.sourceIp = form.sourceIp.data
        custom_urls_data = handle_custom_urls_data(form.customMonitorPage.data)
        schedule_ping(
            job,
            form.settings,
            immediate_exec=form.immediateExec.data,
            prelogin=form.prelogin,
            custom_monitor_urls=custom_urls_data
        )
        job.pingSettings = form.settings
        job.customMonitorPage = CustomMonitorPage.from_dict(custom_urls_data)
        job.save()
    return Success()


# TODO 废弃
@api.route('/vul/schedule', methods=['POST'])
def vul(data=None):
    form = VulSettingsForm(data).validate_for_api()
    if form.is_changed():
        job = form.job
        job.enableMonitor = form.enableMonitor.data
        job.sourceIp = form.sourceIp.data
        custom_urls_data = handle_custom_urls_data(form.customMonitorPage.data)
        schedule_vul(
            job,
            form.settings,
            immediate_exec=form.immediateExec.data,
            addition=form.addition.data,
            prelogin=form.prelogin,
            custom_monitor_urls=custom_urls_data
        )
        job.vulSettings = form.settings
        job.prelogin = form.prelogin
        job.customMonitorPage = CustomMonitorPage.from_dict(custom_urls_data)
        job.save()
    return Success()


# TODO 废弃
@api.route('/vul/manual', methods=['POST'])
def vul_manual(data=None):
    """自定义vul扫描

    """
    if isinstance(data, dict):
        asset_task = data.pop("asset_task", "")
    else:
        asset_task = ""
    form = CustomVulSettingsForm(data).validate_for_api()
    job = form.job
    custom_urls_data, prelogin, alert_setting = get_prelogin_page(asset_task, form)
    _ = schedule_setting(
        scanner.VulScheduler,
        job,
        form.settings,
        trigger_type='manual',
        immediate_exec=form.immediateExec.data,
        addition=form.addition.data,
        alert_setting=alert_setting,
        prelogin=prelogin,
        custom_monitor_urls=custom_urls_data,
        asset_task=asset_task
    )
    return Success()


# TODO 废弃
@api.route('/vul/special', methods=['POST'])
def vul_special():
    """专项vul扫描

    """
    form = SpecialVulSettingsForm().validate_for_api()
    job = form.job
    custom_urls_data = handle_custom_urls_data(form.customMonitorPage.data)
    _ = schedule_setting(
        scanner.SpecialVulScheduler,
        job, form.settings,
        trigger_type='special',
        immediate_exec=form.immediateExec.data,
        addition=form.addition.data,
        prelogin=form.prelogin,
        custom_monitor_urls=custom_urls_data
    )
    return Success()


# TODO 废弃
@api.route('/security_event/schedule', methods=['POST'])
def security_event(data=None):
    """[summary]

    Returns:
        [type]: [description]
    """
    form = SecurityEventSettingsForm(data).validate_for_api()
    custom_urls_data = handle_custom_urls_data(form.customMonitorPage.data)
    if form.is_changed():
        job = form.job
        job.enableMonitor = form.enableMonitor.data
        job.sourceIp = form.sourceIp.data
        schedule_security_event(
            job, form.settings, immediate_exec=form.immediateExec.data,
            addition=form.addition.data,
            custom_monitor_urls=custom_urls_data,
            prelogin=form.prelogin
        )
        job.securityEventSettings = form.settings
        job.prelogin = form.prelogin
        job.customMonitorPage = CustomMonitorPage.from_dict(custom_urls_data)
        job.save()
    return Success()


# TODO 废弃
@api.route('/security_event/manual', methods=['POST'])
def security_event_manual(data=None):
    """自定义security_event扫描

    Returns:
        [type]: [description]
    """
    if isinstance(data, dict):
        asset_task = data.pop("asset_task", "")
    else:
        asset_task = ""
    form = CustomSecurityEventSettingsForm(data).validate_for_api()
    custom_urls_data, prelogin, alert_setting = get_prelogin_page(asset_task, form)
    job = form.job
    _ = schedule_setting(
        scanner.SecurityEventScheduler,
        job, form.settings,
        trigger_type='manual',
        immediate_exec=form.immediateExec.data,
        addition=form.addition.data,
        alert_setting=alert_setting,
        prelogin=prelogin,
        custom_monitor_urls=custom_urls_data,
        asset_task=asset_task
    )
    return Success()


# TODO 废弃
@api.route('/content/schedule', methods=['POST'])
def content(data=None):
    """[summary]

    Returns:
        [type]: [description]
    """
    form = ContentSettingsForm(data).validate_for_api()
    custom_urls_data = handle_custom_urls_data(form.customMonitorPage.data)
    if form.is_changed():
        job = form.job
        job.enableMonitor = form.enableMonitor.data
        job.sourceIp = form.sourceIp.data
        schedule_content(
            job, form.settings, immediate_exec=form.immediateExec.data,
            addition=form.addition.data,
            prelogin=form.prelogin,
            custom_monitor_urls=custom_urls_data
        )
        # 去掉settings.collect.includeUrl的targetUrl
        if urls := form.settings.collect.includeUrl:
            _urls = []
            for url in urls:
                _urls.append(url.replace(job.targetUrl, '', 1))
            form.settings.collect.includeUrl = _urls
        job.contentSettings = form.settings
        job.prelogin = form.prelogin
        job.customMonitorPage = CustomMonitorPage.from_dict(custom_urls_data)
        job.save()
    return Success()


# TODO 废弃
@api.route('/content/manual', methods=['POST'])
def content_manual(data=None):
    """自定义content扫描

    Returns:
        [type]: [description]
    """
    if isinstance(data, dict):
        asset_task = data.pop("asset_task", "")
    else:
        asset_task = ""
    form = CustomContentSettingsForm(data).validate_for_api()
    job = form.job
    custom_urls_data, prelogin, alert_setting = get_prelogin_page(asset_task, form)
    _ = schedule_setting(
        scanner.ContentScheduler,
        job, form.settings,
        trigger_type='manual',
        immediate_exec=form.immediateExec.data,
        addition=form.addition.data,
        alert_setting=alert_setting,
        prelogin=prelogin,
        custom_monitor_urls=custom_urls_data,
        asset_task=asset_task
    )
    return Success()


# TODO 废弃
@api.route('/ssl/schedule', methods=['POST'])
def ssl(data=None):
    """[summary]

    Returns:
        [type]: [description]
    """
    form = SslSettingForm(data).validate_for_api()
    custom_urls_data = handle_custom_urls_data(form.customMonitorPage.data)
    if form.is_changed:
        job = form.job
        job.enableMonitor = form.enableMonitor.data
        job.sourceIp = form.sourceIp.data
        schedule_ssl(
            job,
            form.settings,
            immediate_exec=form.immediateExec.data,
            addition=form.addition.data,
            # prelogin=form.prelogin,
            custom_monitor_urls=custom_urls_data
        )
        job.sslSettings = form.settings
        job.prelogin = form.prelogin
        job.customMonitorPage = CustomMonitorPage.from_dict(custom_urls_data)
        job.save()
    return Success()


# TODO 废弃
@api.route('/ssl/manual', methods=['POST'])
def ssl_manual(data=None):
    """自定义ssl扫描

    Returns:
        [type]: [description]
    """
    if isinstance(data, dict):
        asset_task = data.pop("asset_task", "")
    else:
        asset_task = ""
    form = CustomSslSettingForm(data).validate_for_api()
    custom_urls_data, prelogin, alert_setting = get_prelogin_page(asset_task, form)
    job = form.job
    _ = schedule_setting(
        scanner.SslScheduler,
        job, form.settings,
        trigger_type='manual',
        immediate_exec=form.immediateExec.data,
        addition=form.addition.data,
        alert_setting=alert_setting,
        # prelogin=form.prelogin,
        custom_monitor_urls=custom_urls_data
    )
    return Success()


# TODO 废弃
@api.route('/asset/schedule', methods=['POST'])
def asset_schedule(data=None):
    """[summary]

    Returns:
        [type]: [description]
    """
    form = AssetSettingForm(data).validate_for_api()
    if form.is_changed():
        job, asset = form.job, form.asset
        job.enableMonitor = form.enableMonitor.data
        job.sourceIp = form.sourceIp.data
        schedule_asset(job, asset, form.settings,
                       immediate_exec=form.immediateExec.data,
                       addition=form.addition.data)
        asset.taskSettings = form.settings
        job.save()
        asset.save()
    return Success()


# TODO 废弃
@api.route('/asset/manual', methods=['POST'])
def asset_manual(data=None):
    """自定义资产变动扫描

    Returns:
        [type]: [description]
    """
    form = CustomAssetSettingForm(data).validate_for_api()
    job = form.job
    _ = schedule_setting(
        scanner.AssetScheduler,
        job, form.settings,
        trigger_type='manual',
        immediate_exec=form.immediateExec.data,
        addition=form.addition.data,
        alert_setting=form.alert_setting
    )
    return Success()


@api.route('/job_id_list', methods=['GET'])
def job_id_list():
    """查询用户没有添加 主机监测周期任务、web安全监测周期任务、可用性监测周期任务的资产"""
    form = JobIdForm().validate_for_api()
    uid = form.uid
    asset_type = form.asset_type.data
    trigger_type = form.trigger_type.data
    task_group_type = form.task_group_type.data
    exists = form.exists.data or ""
    if exists.lower() == "true":
        exists = True
    else:
        exists = False

    if asset_type == "host":
        job_query_dict = {'uid': uid, 'assetType': "host", 'verification.isVerified': True}
    else:
        job_query_dict = {'uid': uid, 'assetType': {"$ne": "host"}, 'verification.isVerified': True}
    job_ids = []
    if trigger_type == "schedule":
        asset_query_dict = {'uid': uid, 'triggerType': trigger_type, 'taskGroupType': task_group_type}
        asset_tasks = AssetTasks.objects.find(asset_query_dict)
        job_ids = asset_tasks.distinct('jobId')
    # http任务不用排除已经添加了周期任务的资产
    if job_ids and task_group_type != "http" and not exists:
        job_query_dict['_id'] = {"$nin": job_ids}
    elif job_ids and exists:
        job_query_dict['_id'] = {"$in": job_ids}

    job_list = Job.objects.find(job_query_dict).only('note', 'targetUrl', 'sourceIp', "cydEnabled").order_by('-id')
    data = [{
        'job_id': str(job.pk),
        'note': job.note,
        'target_url': job.targetUrl,
        'source_ip': job.sourceIp,
        "cydEnabled": job.cydEnabled
    } for job in job_list]
    return jsonify({'code': 200, 'message': 'ok', 'data': data})


@api.route('/host_vul_task_list', methods=['GET'])
@param_input(HostVulTaskList, skip_defaults=True)
def host_vul_task_list(query):
    """查询主机资产任务"""
    task_group_type = query.get('task_group_type')
    trigger_type = query.get('trigger_type')
    target_url = query.get('target_url')
    level = query.get('level')
    status = query.get('status')

    page, count = query.get('page', 1), query.get('count', 10)
    sort = ['-id']

    query_dict = {'uid': g.audit_uid, 'taskGroupType': task_group_type}
    if trigger_type:
        query_dict['triggerType'] = trigger_type
    if target_url:
        query_dict['targetUrl'] = {'$regex': re.escape(target_url)}
    if status:
        query_dict['status'] = status
    if level:
        query_dict['lastResult.level'] = level

    total, result = job_util.get_host_vul_task_list(query_dict, page, count, sort, is_web=True)
    data = {
        'total': total,
        'page': page,
        'count': count,
        'asset_task_list': result,
    }

    return jsonify({'code': 0, 'message': 'success', 'data': data})


@api.route('/admin_host_vul_task_list', methods=['GET'])
@permission_required('admin')
@param_input(AdminHostVulTaskList, skip_defaults=True)
def admin_host_vul_task_list(query):
    username = query.get('username')
    target_url = query.get('target_url')
    task_group_type = query.get('task_group_type')
    status = query.get('status')
    trigger_type = query.get('trigger_type')

    user_id_list = []
    if username:
        uses = User.objects.find({"username": {'$regex': re.escape(username)}})
        user_id_list = [user.pk for user in uses]
    page, count = query.get('page', 1), query.get('perPage', 10)
    sort = ['-id']
    query_dict = {'taskGroupType': task_group_type}
    if user_id_list:
        query_dict = {'uid': {"$in": user_id_list}, 'taskGroupType': task_group_type}
    if target_url:
        query_dict['targetUrl'] = {'$regex': re.escape(target_url)}
    if trigger_type:
        query_dict['triggerType'] = trigger_type
    if status:
        query_dict['status'] = status
    total, results = job_util.get_host_vul_task_list(query_dict, page, count, sort)
    user_ids = [ObjectId(item.get('uid')) for item in results if item.get('uid')]
    uses = User.objects.find({"_id": {"$in": user_ids}})
    user_maps = {str(user.pk): user.username for user in uses}
    for item in results:
        item['username'] = user_maps.get(item.get('uid'), "")

    page_info = {
        'currentPage': page,
        'hasNextPage': True if page * count < total else False,
        'hasPreviousPage': True if page > 1 else False,
        'itemCount': total,
        'pageCount': ((total - 1) // count) + 1,
        'perPage': count,
    }

    data = {
        'page': page,
        'count': total,
        'pageInfo': page_info,
        'results': results,
    }

    return jsonify({'code': 0, 'message': '任务保存成功', 'data': data})


@api.route('/host_vul_task_settings', methods=['GET'])
@param_input(HostVulTaskSettings, skip_defaults=True)
def host_vul_task_settings(query):
    """查询主机资产任务"""
    uid = g.audit_uid
    if g.role.name == '管理员':
        uid = query.get('uid')

    asset_task_id = query.get('asset_task_id')
    asset_task = AssetTasks.objects.find_one({'_id': asset_task_id, 'uid': uid})
    if not asset_task:
        raise ParameterError(msg='当前任务不存在')

    job = Job.objects.filter(pk=asset_task.jobId).first()

    data = {
        'asset_task_id': str(asset_task.pk),
        'job_id': str(asset_task.jobId),
        'job_name': job.note,
        'source_ip': job.sourceIp,
        'target_url': job.targetUrl,
        "name": asset_task.name,
        "trigger_type": asset_task.triggerType,
        "enable_monitor": asset_task.enableMonitor,
    }

    asset_task = asset_task.to_mongo().to_dict()
    host_vul_settings = asset_task.get('hostVulSettings', {})
    data.update(host_vul_settings)

    return jsonify({'code': 0, 'message': 'ok', 'data': data})


@api.route('/task_summary', methods=['GET'])
@param_input(TaskSummary, skip_defaults=True)
def task_summary(query):
    uid = g.audit_uid
    task_group_type = query.get('task_group_type')
    task_group_type_maps = {
        'host': 'host',
        'web': 'web',
        'availability': 'web',
    }

    schedule = 0
    manual = 0
    running_task_count = 0
    job_set = set()
    schedule_task_list = []
    manual_task_id_list = []
    has_monitor_set = set()
    result_ids = set()

    asset_tasks = AssetTasks.objects.find({'uid': uid, 'taskGroupType': task_group_type})
    for asset_task in asset_tasks:
        if asset_task.hostVulResultId:
            result_ids.add(asset_task.hostVulResultId)
        if asset_task.hostVulSettings.taskId:
            has_monitor_set.add(asset_task.jobId)
        if asset_task.triggerType == 'schedule':
            schedule += 1
            if asset_task.enableMonitor:
                job_set.add(asset_task.jobId)
                running_task_count += 1
            schedule_task_list.append(asset_task)
        else:
            manual += 1
            manual_task_id_list.append(asset_task.hostVulSettings.taskId)

    jobs = Job.objects.find({'uid': uid, 'assetType': task_group_type_maps[task_group_type]})
    job_total = jobs.count()

    # 获取单次任务
    running_tasks = set()
    status_list = [TaskStatus.waiting.value, TaskStatus.active.value]
    tasks = Task.objects.find({"taskId": {"$in": manual_task_id_list}})
    for task in tasks:
        if task.status in status_list:
            running_tasks.add(task.taskId)

    vul_task_count = 0
    result_tasks = Task.objects.find({"_id": {"$in": list(result_ids)}})
    for result_task in result_tasks:
        if result_task.result.warningCount:
            vul_task_count += 1

    data = {
        'task': {'total': manual + schedule, 'running': running_task_count + len(running_tasks), 'vul': vul_task_count},
        'trigger_type': {'schedule': schedule, 'manual': manual},
        'job_summary': {'has_monitor': len(has_monitor_set), 'no_monitor': job_total - len(has_monitor_set)}
    }

    return jsonify({'code': 0, 'message': '成功', 'data': data})


@api.route('/job_count', methods=['GET'])
@param_input(JobCount, skip_defaults=True)
def job_count(query):
    uid = g.audit_uid
    query_dict = {"uid": uid}
    job_query_dict = {"uid": uid}
    if g.role.name == '管理员':
        query_dict = dict()
        job_query_dict = dict()

    task_group_type_maps = {
        'host': 'host',
        'web': 'web',
        'availability': 'web',
    }

    task_monitor = 0
    manual_task_id_list = []
    task_group_type = query.get('task_group_type')
    query_dict.update({'taskGroupType': task_group_type})
    asset_tasks = AssetTasks.objects.find(query_dict)
    task_total = asset_tasks.count()
    for asset_task in asset_tasks:
        if asset_task.triggerType == 'schedule':
            if asset_task.enableMonitor:
                task_monitor += 1
        else:
            manual_task_id_list.append(asset_task.hostVulSettings.taskId)

    # 获取单次任务
    status_list = [TaskStatus.waiting.value, TaskStatus.active.value]
    tasks = Task.objects.find({"taskId": {"$in": manual_task_id_list}})
    for task in tasks:
        if task.status in status_list:
            task_monitor += 1

    asset_total = 0
    asset_monitor = 0
    job_query_dict.update({'assetType': task_group_type_maps[task_group_type]})
    aggregate = [
        {'$match': job_query_dict},
        {'$group': {'_id': '$_id', 'count': {'$sum': 1}}},
        {'$lookup': {'from': "tasks", 'localField': "_id", 'foreignField': "jobId", 'as': "task"}},
    ]
    jobs = Job.objects.aggregate(aggregate)
    for item in jobs:
        asset_total += 1
        if item.get('task'):
            asset_monitor += 1

    data = {'task_monitor': task_monitor, 'task_total': task_total, 'asset_total': asset_total,
            'asset_monitor': asset_monitor}

    return jsonify({'code': 0, 'message': '成功', 'data': data})


@api.route('/host_vul_task', methods=['POST'])
@param_input(HostVulTask, param='json', skip_defaults=True)
def host_vul_task(data):
    """添加主机资产任务"""
    uid = g.audit_uid
    if g.role.name == '管理员':
        uid = data.get('uid')

    name = data.get('name')
    asset_task_id = data.get('asset_task_id', '')
    trigger_type = data.get('trigger_type')

    job_ids = data.get('job_ids', [])

    interval = data.get('interval', 86400000)
    if interval < 1000:
        raise ParameterError(msg='频率不能小于1000')

    if not NAME_RE.fullmatch(name):
        raise CustomNameError()

    timeout = data.get('timeout', 2)
    is_detect_os_enabled = data.get('is_detect_os_enabled', False)
    is_tcp_enabled = data.get('is_tcp_enabled', True)
    is_udp_enabled = data.get('is_udp_enabled', False)

    if asset_task_id:
        exist = AssetTasks.objects.find_one({'_id': asset_task_id, 'uid': uid})
        if not exist:
            raise ParameterError(msg='当前任务不存在')

        if len(job_ids) > 1:
            raise ParameterError(msg='编辑只能选择一个资产')
    else:
        balances = Balances.objects.find_one({'uid': uid})
        if not balances:
            raise ParameterError(msg='无关联余额信息')
        if trigger_type == 'manual':
            if not balances.feature.hostVulManualTask:
                raise ParameterError(msg='当前套餐不支持该功能')
            if balances.balance.hostVulManualTask.used == balances.balance.hostVulManualTask.total:
                raise ParameterError(msg='单次任务次数已用完')
            if balances.balance.hostVulManualTask.used + len(job_ids) > balances.balance.hostVulManualTask.total:
                raise ParameterError(msg='单次任务次数已不足')
        else:
            if not balances.feature.hostVulTask:
                raise ParameterError(msg='当前套餐不支持该功能')

    # 校验当前任务名称已存在
    exist = AssetTasks.objects.find({'uid': uid, 'jobId': {"$in": job_ids}, 'taskGroupType': 'host',
                                     'triggerType': 'schedule'})
    schedule_job_exists = set([item.jobId for item in exist])

    # 主机监测默认模版, 当前阶段引擎不支持，默认传0, 以后可能做到页面用户选择
    network_scan_profile = NETWORK_SCAN_PROFILE
    is_detect_port_enabled = True
    enable_monitor = True

    # 单次任务去掉频率
    if trigger_type == 'manual':
        interval = 0

    affects = []
    jobs = Job.objects.find({'_id': {'$in': job_ids}, 'uid': uid, 'verification.isVerified': True, 'assetType': 'host'})
    for job in jobs:
        job_id = job.pk
        # 周期性任务已经存在
        if trigger_type == 'schedule' and not asset_task_id and job_id in schedule_job_exists:
            continue
        if asset_task_id:
            asset_task = AssetTasks.objects.find_one({'_id': asset_task_id, 'uid': uid})
            enable_monitor = asset_task.enableMonitor
        else:
            asset_task = AssetTasks()
            if trigger_type == 'manual':
                balances = Balances.objects.find_one({'uid': uid})
                balances.update(inc__balance__hostVulManualTask__used=1)
        asset_task.name = name
        asset_task.uid = uid
        asset_task.jobId = job_id
        asset_task.isVerified = True
        asset_task.taskGroupType = 'host'
        asset_task.triggerType = trigger_type
        asset_task.enableMonitor = enable_monitor
        asset_task.taskType = 'hostVul'
        asset_task.targetUrl = job.targetUrl
        asset_task.hostVulSettings.interval = interval
        asset_task.hostVulSettings.timeout = timeout
        asset_task.hostVulSettings.network_scan_profile = network_scan_profile
        asset_task.hostVulSettings.is_detect_os_enabled = is_detect_os_enabled
        asset_task.hostVulSettings.is_detect_port_enabled = is_detect_port_enabled
        asset_task.hostVulSettings.is_tcp_enabled = is_tcp_enabled
        asset_task.hostVulSettings.is_udp_enabled = is_udp_enabled
        # 主机监测任务暂时使用 资产默认告警设置
        asset_task.alertSettings = job.alertSettings
        asset_task.save()
        affects.append(job.targetUrl)

        if enable_monitor:
            _data = {
                'hostVulSettings': asset_task.hostVulSettings.to_dict(),
                'jobId': str(job_id),
                'asset_task_id': str(asset_task.pk),
                'enableMonitor': enable_monitor,
            }
            if asset_task.triggerType == 'schedule':
                host_vul_schedule(_data)

            if asset_task.triggerType == 'manual' and not asset_task_id:
                _data['immediateExec'] = True
                host_vul_manual(_data)

    if affects:
        log_info = {
            'affects': affects,
            'actionMessage': f'主机资产漏洞监测',
            'detail': f'主机资产漏洞监测{"编辑" if asset_task_id else "添加"}: 任务名称：{name}, 资产地址: {",".join(affects)}'
        }
        CAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])
        if asset_task_id:
            return jsonify({'code': 0, 'message': '保存成功，您修改的配置将在下一周期生效'})
        return jsonify({'code': 0, 'message': '任务添加成功'})
    else:
        raise ParameterError(msg='主机资产不存在或任务已经存在')


@api.route('/host_vul_task_delete', methods=['POST'])
@param_input(HostVulTaskDelete, param='json', skip_defaults=True)
def host_vul_task_delete(data):
    """删除主机资产任务"""
    uid_list = [g.audit_uid]
    if g.role.name == '管理员':
        uid_list = data.get('uid_list', [])

    asset_task_ids = data.get('asset_task_ids')
    asset_task_list = AssetTasks.objects.find({'_id': {'$in': asset_task_ids}, 'uid': {"$in": uid_list}})
    if not asset_task_list:
        raise ParameterError(msg='当前任务不存在')

    task_ids = []
    affects = []
    log_detail_list = []
    job_ids = set()
    for asset_task in asset_task_list:
        job_ids.add(asset_task.jobId)
        if asset_task.hostVulSettings.taskId:
            task_ids.append(asset_task.hostVulSettings.taskId)
        affects.append(asset_task.targetUrl)
        log_detail_list.append(f'任务名称：{asset_task.name}, 资产地址: {asset_task.targetUrl}')

    scanner.BatchRemoveTask(task_ids).batch_remove()
    Task.objects.filter(taskId__in=task_ids, uid__in=uid_list).delete()
    AssetTasks.objects.filter(pk__in=asset_task_ids, uid__in=uid_list).delete()

    # 计算资产最新最新安全状态
    job_map = {j.id: j for j in Job.objects.filter(pk__in=list(job_ids))}
    for job_id in job_ids:
        if job := job_map.get(job_id):
            job_util.update_last_host_security_status(job)

    log_info = {
        'affects': affects,
        'actionMessage': '删除主机资产任务',
        'detail': f'删除主机资产任务，{"; ".join(log_detail_list)}'
    }
    DAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])

    return jsonify({'code': 0, 'message': '删除任务成功'})


@api.route('/host_vul_task_switch', methods=['POST'])
@param_input(HostVulTaskSwitch, param='json', skip_defaults=True)
def host_vul_task_switch(data):
    """监测开关"""
    uid = g.audit_uid
    if g.role.name == '管理员':
        uid = data.get('uid')

    asset_task_id = data['asset_task_id']
    enable_monitor = data['enable_monitor']
    asset_task = AssetTasks.objects.find_one({'_id': asset_task_id, 'uid': uid})
    if not asset_task:
        raise ParameterError(msg='当前任务不存在')

    if asset_task.triggerType != 'schedule':
        raise ParameterError(msg='任务类型错误, 不是周期任务')

    job = Job.objects.find_one({'_id': asset_task.jobId, 'uid': uid, 'verification.isVerified': True,
                                'assetType': 'host'})
    if not job:
        raise ParameterError(msg='当前资产未认证或者不存在')

    if enable_monitor:
        balances = Balances.objects.find_one({'uid': uid})
        if not balances.feature.hostVulTask:
            raise ParameterError(msg='当前套餐不支持该功能')

    _data = {
        'hostVulSettings': asset_task.hostVulSettings.to_dict(),
        'jobId': str(asset_task.jobId),
        'asset_task_id': str(asset_task.pk),
        'enableMonitor': enable_monitor,
    }
    host_vul_schedule(_data)

    log_info = {
        'affects': job.targetUrl,
        'actionMessage': '主机资产漏洞监测开关',
        'detail': f'任务监测开关: {"开启" if enable_monitor else "关闭"}监测开关'
    }
    UAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])

    return jsonify({'code': 0, 'message': f'{"开启" if enable_monitor else "关闭"}成功'})


@api.route('/host_vul/schedule', methods=['POST'])
def host_vul_schedule(data=None):
    form = HostVulSettingForm(data).validate_for_api()
    if form.is_changed:
        job = form.job
        asset_task = form.asset_task

        balances = Balances.objects.find_one({'uid': asset_task.uid})
        if not balances:
            raise ParameterError(msg='无关联余额信息')
        if asset_task.hostVulSettings.interval < balances.balance.hostVulMaxFreq.total:
            asset_task.hostVulSettings.interval = balances.balance.hostVulMaxFreq.total

        asset_task.enableMonitor = form.enableMonitor.data
        try:
            schedule_host_vul(
                job,
                asset_task,
                form.settings,
                immediate_exec=form.immediateExec.data,
                addition=form.addition.data,
            )
        except Exception as e:
            app.logger.exception(e)
            asset_task.enableMonitor = False
            asset_task.save()
            raise ParameterError(msg='任务下发失败')

        asset_task.hostVulSettings = form.settings
        asset_task.save()
    return Success()


@api.route('/host_vul/manual', methods=['POST'])
def host_vul_manual(data=None):
    form = HostVulSettingForm(data).validate_for_api()
    job = form.job
    asset_task = form.asset_task
    asset_task.enableMonitor = form.enableMonitor.data

    try:
        task_id = schedule_setting(
            scanner.HostVulScheduler,
            job, form.settings,
            trigger_type='manual',
            immediate_exec=form.immediateExec.data,
            addition=form.addition.data,
            alert_setting=asset_task.alertSettings,
            asset_task=asset_task,
        )
        form.settings.taskId = task_id
    except Exception as e:
        app.logger.exception(e)
        asset_task.enableMonitor = False
        asset_task.save()
        raise ParameterError(msg='任务下发失败')

    asset_task.hostVulSettings = form.settings
    asset_task.save()

    return Success()


@api.route('/ipv6/manual', methods=['POST'])
def ipv6_manual(data=None):
    form = CustomIpv6SettingForm(data).validate_for_api()
    target_url_list = []
    job_list_length = len(form.job_list)
    check_all = False
    asset_task_form = None

    # retry
    asset_task_id = form.asset_task_id.data

    for job in form.job_list:
        if not asset_task_id or not (asset_task := AssetTasks.objects.filter(pk=asset_task_id).first()):
            asset_task_data = {'jobId': job.id, 'uid': job.uid, 'name': form.name.data}
            asset_task_form = CustomIPv6AssetTaskForm(asset_task_data)
            if not check_all:
                asset_task_form.balance_one.inc_manual_task_used(TaskType.ipv6.value, job_list_length, only_check=True)
                check_all = True
            asset_task = asset_task_form.create()
        else:
            asset_task_form = CustomIPv6AssetTaskForm(asset_task.to_mongo().to_dict())
        try:
            schedule_setting(
                scanner.IPv6Scheduler,
                job,
                asset_task=asset_task,
                setting=asset_task.ipv6Settings,
                trigger_type=TaskTriggerType.manual.value,
                immediate_exec=True,
                addition=form.addition.data,
            )
            if not asset_task_id:
                asset_task_form.balance_one.inc_manual_task_used(TaskType.ipv6.value, 1)
        except Exception as e:
            if not asset_task_id:
                asset_task.delete()
            raise e

        target_url_list.append(job.targetUrl)

    CAssetTaskLog(affects=target_url_list).info(
        '批量下发IPv6合规检测单次任务',
        detail=f"批量下发IPv6合规检测单次任务，任务名称: {form.name.data}, 资产地址有: {', '.join(target_url_list)}.")

    asset_task_form.balance_one.obj_balance.reload()
    return jsonify({
        'code': 200,
        'results': {
            'total': asset_task_form.balance_one.obj_balance.balance.ipv6ManualTask.total,
            'used': asset_task_form.balance_one.obj_balance.balance.ipv6ManualTask.used
        }
    })


@api.route("/web_security/task/add", methods=['POST'])
def web_security_schedule_add():
    """
    创建web安全监测周期任务
    """
    form_obj = WebSecurityTaskAddForm().validate_for_api()
    prelogin = form_obj.prelogin
    affects = []
    success, fail = 0, 0
    for job in form_obj.job_list:
        asset_task = form_obj.save_asset(form_obj, job, prelogin, True, "schedule", affects)
        if not asset_task:
            continue
        try:
            enable_web_sec_task(asset_task, job=job)
            success += 1
        except:
            web_security_schedule_delete({"asset_task_ids": [asset_task.id, ]})
            fail += 1
    if affects:
        log_info = {
            'affects': affects,
            'actionMessage': f'web安全监测',
            'detail': f'web安全监测添加周期任务: 任务名称：{form_obj.name.data}, 资产地址: {",".join(affects)}'
        }
        CAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])

    return jsonify({'code': 200, 'message': f'任务下发成功{success}个, 失败{fail}个', 'status': 'success'})


@api.route("/web_security/task/manual/add", methods=['POST'])
def web_security_manual_add():
    """创建单次任务"""
    form_obj = WebSecurityManualTaskAddForm().validate_for_api()
    prelogin = form_obj.prelogin
    affects = []
    balance = form_obj.balance
    type_map = {k: 0 for k in WS_TASK_GROUP_TUPLE if TaskTriggerType.manual.value in TASK_TYPE_CONFIG[k]["trigger"]}
    success, fail = 0, 0
    for job in form_obj.job_list:
        asset_task = form_obj.save_asset(form_obj, job, prelogin, True, "manual", affects)
        if not asset_task:
            continue
        try:
            enable_web_sec_task(asset_task, job=job)
            success += 1
        except:
            web_security_schedule_delete({"asset_task_ids": [asset_task.id, ]})
            fail += 1
            continue
        for i in type_map.keys():
            setting = getattr(asset_task, f"{i}Settings", "")
            if setting and setting.enable:
                if i == "ssl" and not asset_task.targetUrl.startswith("https"):
                    continue
                type_map[i] += 1
    for key, num in type_map.items():
        if num:
            BalancesOne(obj_balances=balance).inc_manual_task_used(key, num)
    if affects:
        log_info = {
            'affects': affects,
            'actionMessage': f'web安全监测',
            'detail': f'web安全监测添加单次任务: 任务名称：{form_obj.name.data}, 资产地址: {",".join(affects)}'
        }
        CAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])

    return jsonify({'code': 200, 'message': f'任务下发成功{success}个, 失败{fail}个', 'status': 'success'})


@api.route("/web_security/task/update", methods=['POST'])
def web_security_schedule_update():
    """
    修改web安全监测任务， 只能修改周期任务
    """
    form_obj = WebSecurityTaskUpdateForm().validate_for_api()
    asset_task = form_obj.asset_task
    asset_task.prelogin = form_obj.prelogin
    asset_task.scanSpeedConfig = form_obj.scan_speed_config
    asset_task.customMonitorPage = form_obj.custom_monitor_page
    job = form_obj.job
    for task_type in WS_TASK_GROUP_TUPLE:
        task_type_line = TASK_TYPE_CONFIG.get(task_type, {}).get("line")
        if form := getattr(form_obj, f"{task_type_line}_form", None):
            setattr(asset_task, f"{task_type}Settings", form.settings)
    not_ssl_task_enable = any([getattr(asset_task, f"{task_type}Settings").enable for task_type in
                               WS_TASK_GROUP_TUPLE if task_type != TaskType.ssl.value])
    is_only_ssl = asset_task.sslSettings.enable and not not_ssl_task_enable
    if is_only_ssl and not asset_task.targetUrl.startswith("https"):
        return jsonify({'code': 200, 'message': 'ssl任务只有https协议的服务支持', 'status': 'success'})
    asset_task.name = form_obj.name.data
    asset_task.ws_task_time = WsTaskTime.from_dict(form_obj.ws_task_time)
    asset_task.save()
    enable_web_sec_task(asset_task, job=job)
    log_info = {
        'affects': [job.targetUrl],
        'actionMessage': f'web安全监测',
        'detail': f'修改web安全监测任务: 任务名称：{form_obj.name.data}, 资产地址: {",".join([job.targetUrl])}'
    }
    UAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])
    return jsonify({'code': 200, 'message': '保存成功，您修改的配置将在下一周期生效', 'status': 'success'})


@api.route("/web_security/task/del", methods=['POST'])
def web_security_schedule_delete(data=None):
    """
    删除web安全监测任务
    """
    form = WebSecurityBatchForm(data).validate_for_api()
    asset_tasks = form.asset_tasks
    affects = set()
    task_names = []
    job_map = form.job_map
    delete_asset_task_ids = []
    all_remove_ids = []
    for asset_task in asset_tasks:
        job = job_map.get(asset_task.jobId)
        remove_ids = asset_task.get_ws_task_ids()
        if remove_ids:
            all_remove_ids.extend(remove_ids)
        affects.add(job.targetUrl)
        task_names.append(asset_task.name)
        delete_asset_task_ids.append(asset_task.id)
    scanner.BatchRemoveTask(all_remove_ids).batch_remove()
    Task.objects(taskId__in=all_remove_ids).delete()
    TaskSpiderUrl.objects(taskId__in=all_remove_ids).delete()
    ChangeCheckBasepage.objects(task_id__in=all_remove_ids).delete()
    AssetTasks.objects(id__in=delete_asset_task_ids).delete()

    # 计算资产最新最新安全状态
    for _, job in job_map.items():
        job_util.update_last_web_security_status(job)
    affects = list(affects)
    if not data:
        log_info = {
            'affects': affects,
            'actionMessage': f'web安全监测',
            'detail': f'删除web安全监测任务: 任务名称：{",".join(task_names)}, 资产地址: {",".join(affects)}'
        }
        DAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])
    return jsonify({'code': 200, 'message': '操作成功', 'status': 'success'})


@api.route("/web_security/task/switch", methods=['POST'])
def web_security_schedule_switch():
    """
    开启｜关闭web安全周期任务
    """
    form_obj = WebSecurityBatchSwitchForm().validate_for_api()
    enable = form_obj.enable.data
    job_map = form_obj.job_map
    fail_affects = set()
    success = 0
    fail = 0
    affects = []
    name_list = []
    for asset_task in form_obj.asset_tasks:
        job = job_map.get(asset_task.jobId)
        if enable and not job.verification.isVerified:
            fail_affects.add(job.targetUrl)
            fail += 1
            continue
        asset_task.enableMonitor = enable
        asset_task.save()
        enable_web_sec_task(asset_task)
        affects.append(asset_task.targetUrl)
        name_list.append(asset_task.name)
        success += 1
    if affects:
        log_info = {
            'affects': affects,
            'actionMessage': f'web安全监测',
            'detail': f'{"关闭" if not enable else "开启"}web安全监测任务: 任务名称：{",".join(name_list)}, 资产地址: {",".join(affects)}'
        }
        UAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])
    enable_msg = f"开启成功{success}个任务"
    if fail:
        enable_msg += f", {'、'.join(fail_affects)} 未认证, {fail}个任务开启失败"
    msg = "关闭成功，下个周期将不在执行" if not enable else enable_msg
    return jsonify({'code': 200, 'message': msg, 'status': 'success'})


@api.route("/available/task/add", methods=['POST'])
def available_schedule_add():
    """可用性周期任务增加"""
    form_obj = AvailableTaskAddForm().validate_for_api()
    task_type = form_obj.task_type.data
    affects = []
    task_count = 0
    if job_count := len(form_obj.job_list) > 50:
        app.logger.warning(f"{g.user.username}下发资产数量大于50，资产量{job_count}")
    asset_task_ids = []
    success, fail = 0, 0
    for job in form_obj.job_list:
        if setting_form := form_obj.job_form_map.get(job.id):
            for setting in setting_form.settings:
                asset_task = form_obj.save_asset_task(setting, job, True, "schedule", task_type)
                try:
                    enable_available_task(asset_task, job)
                    success += 1
                except:
                    available_schedule_delete({"asset_task_ids": [asset_task.id, ]})
                    fail += 1
                    continue
                task_count += 1
                asset_task_ids.append(asset_task.id)
            affects.append(job.targetUrl)
        settings = [getattr(a, f"{task_type}Settings") for a in
                    AssetTasks.objects.filter(jobId=job.id, taskGroupType=task_type) if getattr(a, f"{task_type}Settings", "")]
        setattr(job, f"{task_type}Settings", settings)
        job.save()
    balance_task_count_sync(g.user.id, form_obj.balance)
    if affects:
        try:
            log_detail = get_available_task_log_detail(asset_task_ids)
        except:
            log_detail = ""
        log_info = {
            'affects': affects,
            'actionMessage': f'可用性监测',
            'detail': f'新建可用性监测任务:  {log_detail}'
        }
        CAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])
    return jsonify({'code': 200, 'message': f'任务下发成功{success}个, 失败{fail}个', 'status': 'success'})


@api.route("/available/task/update", methods=['POST'])
def available_schedule_update():
    """可用性周期任务修改"""
    form_obj = AvailableTaskUpdateForm().validate_for_api()
    setting = form_obj.setting
    asset_task = form_obj.asset_task
    job = form_obj.job
    setattr(asset_task, f"{asset_task.taskGroupType}Settings", setting)
    asset_task.name = setting.name
    asset_task.save()
    enable_available_task(asset_task, job)
    job_settings = getattr(job, f"{asset_task.taskGroupType}Settings", [])
    index = -1
    for i, item in enumerate(job_settings):
        if setting.target == item.target:
            index = i
            break
    job_settings[index] = setting
    job.save()
    balance = Balances.objects.filter(uid=job.uid).first()
    balance_task_count_sync(job.uid, balance)
    try:
        log_detail = get_available_task_log_detail([asset_task.id, ])
    except:
        log_detail = ""
    log_info = {
        'affects': [job.targetUrl],
        'actionMessage': f'可用性任务',
        'detail': f'修改可用性任务: 任务名称：{asset_task.name}, {log_detail}'
    }
    UAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])
    return jsonify({'code': 200, 'message': '保存成功，您修改的配置将在下一周期生效', 'status': 'success'})


@api.route("/available/task/del", methods=['POST'])
def available_schedule_delete(data=None):
    """可用性周期任务删除"""
    form_obj = AvailableBatchForm().validate_for_api()
    asset_tasks = form_obj.asset_tasks
    affects = set()
    task_names = []
    job_map = form_obj.job_map
    job_task_id_map = {i: [] for i, _ in job_map.items()}
    delete_asset_task_ids = []
    all_remove_ids = []
    user_task_map = {}
    for asset_task in asset_tasks:
        remove_ids = asset_task.get_available_task_ids()
        if remove_ids:
            all_remove_ids.extend(remove_ids)
            job_task_id_map[asset_task.jobId].extend(remove_ids)
        affects.add(asset_task.targetUrl)
        delete_asset_task_ids.append(asset_task.id)
        task_names.append(asset_task.name)
        if not user_task_map.get(asset_task.uid, {}).get(asset_task.taskGroupType, 0):
            user_task_map.update({asset_task.uid: {asset_task.taskGroupType: 1}})
        else:
            user_task_map[asset_task.uid][asset_task.taskGroupType] += 1
    scanner.BatchRemoveTask(all_remove_ids).batch_remove()
    Task.objects(taskId__in=all_remove_ids).delete()
    AssetTasks.objects(id__in=delete_asset_task_ids).delete()
    for jid, job in job_map.items():
        job_task_ids = job_task_id_map.get(jid, [])
        job.httpSettings = [hs for hs in job.httpSettings if hs.taskId not in job_task_ids]
        job.pingSettings = [ps for ps in job.pingSettings if ps.taskId not in job_task_ids]
        job.save()
    # 更新套餐
    balance_map = {b.uid: b for b in Balances.objects.filter(uid__in=list(user_task_map.keys()))}
    for uid, balance in balance_map.items():
        balance_task_count_sync(uid, balance)
    affects = list(affects)
    if not data:
        log_info = {
            'affects': affects,
            'actionMessage': f'可用性任务',
            'detail': f'删除可用性任务: 任务名称：{",".join(task_names)}, 资产地址: {",".join(affects)}'
        }
        DAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])
    return jsonify({'code': 200, 'message': '操作成功', 'status': 'success'})


@api.route("/available/task/switch", methods=['POST'])
def available_schedule_switch():
    """
    开启｜关闭可用性周期任务
    """
    form_obj = AvailableBatchSwitchForm().validate_for_api()
    enable = form_obj.enable.data
    job_map = form_obj.job_map
    affects = set()
    name_list = []
    fail_affects = set()
    success = 0
    fail = 0
    for asset_task in form_obj.asset_tasks:
        job = job_map.get(asset_task.jobId)
        if enable and not job.verification.isVerified:
            fail_affects.add(job.targetUrl)
            fail += 1
            continue
        asset_task.enableMonitor = enable
        setting = getattr(asset_task, f"{asset_task.taskGroupType}Settings")
        setting.enable = enable
        if job_settings := getattr(job, f"{asset_task.taskGroupType}Settings"):
            for s in job_settings:
                if s.target == setting.target:
                    s.enable = enable
                    break
            job.save()
        affects.add(asset_task.targetUrl)
        name_list.append(asset_task.name)
        asset_task.save()
        enable_available_task(asset_task)
        balance = Balances.objects.filter(uid=job.uid).first()
        balance_task_count_sync(job.uid, balance)
        success += 1
    affects = list(affects)
    if affects:
        log_info = {
            'affects': affects,
            'actionMessage': f'可用性任务',
            'detail': f'{"关闭" if not enable else "开启"}可用性任务: 任务名称：{",".join(name_list)}, 资产地址: {",".join(affects)}'
        }
        UAssetTaskLog(**log_info).info(log_info['actionMessage'], log_info['detail'])
    enable_msg = f"开启成功{success}个任务"
    if fail:
        enable_msg += f", {'、'.join(fail_affects)} 未认证, {fail}个任务开启失败"
    msg = "关闭成功，下个周期将不在执行" if not enable else enable_msg
    return jsonify({'code': 200, 'message': msg, 'status': 'success'})


SCHEDULE_MAP = {
    'vul': vul,
    'ssl': ssl,
    'securityEvent': security_event,
    'asset': asset_schedule,
    'http': http,
    'ping': ping,
    'content': content,
}


MANUAL_MAP = {
    'vul': vul_manual,
    'ssl': ssl_manual,
    'securityEvent': security_event_manual,
    'asset': asset_manual,
    'content': content_manual,
    'hostVul': host_vul_manual,
    'ipv6': ipv6_manual
}


@api.route('/schedule/<string:switch>', methods=['POST'])
@permission_required('admin')
def schedule_disabled(switch):
    # TODO:
    #  Call schedule async.
    #  Shift the modification of httpEnable/pingEnable's used to schedule.
    request_args = json.loads(request.data)
    job_id_list = request_args.get('job_ids', [])
    close_only = request_args.get('close_only', False)

    if switch not in ('disable', 'enable'):
        raise ParameterError(msg=f"Unexpected params '{switch}'.")
    switch = True if switch != 'disable' else False

    for job_id in job_id_list:
        job = Job.objects.get(pk=job_id)
        asset = JobAsset.objects.get(jobId=job_id)
        balances = Balances.objects.get(uid=job.uid)

        balance_update_dict = {'inc__balance__domainEnable__used': 1 if switch else -1}
        if not close_only and job.enableMonitor == switch:
            continue

        for task_type in MONITOR_TYPE_TUPLE:
            if task_type == TaskType.asset.value:
                settings = asset.taskSettings
            else:
                settings = getattr(job, f"{task_type}Settings")

            schedule = SCHEDULE_MAP[task_type]

            enabled_count = 0
            if hasattr(settings, 'to_dict'):
                settings = settings.to_dict()
            else:
                # TODO: Temp
                # settings = [item.to_dict() for item in settings]
                temp = []
                for item in settings:
                    if item.enable:
                        enabled_count += 1
                    temp.append(item.to_dict())
                settings = temp
                # TODO

            data = {
                "immediateExec": False,
                "jobId": job_id,
                "sourceIp": job.sourceIp,
                "enableMonitor": switch,
                f"{task_type}Settings": settings
            }

            schedule(data)

            if task_type in (TaskType.http.value, TaskType.ping.value):
                balance_update_dict[f"inc__balance__{task_type}MaxTaskEnable__used"] = enabled_count if switch else -enabled_count
            elif task_type == TaskType.vul.value and settings.get('enable') and settings.get('enableSitePortraitTriggerMonitor'):
                balance_update_dict['inc__balance__sitePortrait__used'] = 1 if switch else -1

        if not close_only:
            job.update(enableMonitor=switch)
            balances.update(**balance_update_dict)

    return Success()


@api.route('/tasks/remove', methods=['POST'])
def tasks_remove():
    """批量删除task, 当taskIds为空时, 移除job下所有的task

    Returns:
        [type]: [description]
    """
    form = BatchRemoveTaskForm().validate_for_api()
    if not form.taskIds.data:
        task_ids = form.job.get_task_ids(asset=form.asset)
        scanner.BatchRemoveTask(task_ids).batch_remove()
        tasks = Task.objects(jobId=form.jobId.data)
        tasks.delete()
        TaskSpiderUrl.objects(jobId=form.jobId.data).delete()
        ChangeCheckBasepage.objects(job_id=form.jobId.data).delete()
    else:
        task_ids = form.job.get_task_ids(asset=form.asset) & set(form.taskIds.data)
        scanner.BatchRemoveTask(task_ids).batch_remove()
        tasks = Task.objects(
            jobId=form.jobId.data,
            taskId__in=task_ids)
        tasks.delete()
        TaskSpiderUrl.objects(jobId=form.jobId.data, taskId__in=task_ids).delete()
        ChangeCheckBasepage.objects(job_id=form.jobId.data, task_id__in=task_ids).delete()
    return Success()


@api.route('/tasks/scanner/remove', methods=['POST'])
@permission_required('admin')
def tasks_scanner_remove():
    form = BatchRemoveTaskForm().validate_for_api()
    task_ids = form.job.get_task_ids(asset=form.asset)
    try:
        # 删除任务后， UI测设置为关闭
        asset_tasks = AssetTasks.objects.filter(jobId=form.job.id, triggerType="schedule")
        asset_tasks.update(enableMonitor=False)
        for at in asset_tasks:
            task_ids.update({i for i in at.get_task_ids_v2().values() if i})
    except Exception as e:
        app.logger.exception(e)

    scanner.BatchRemoveTask(task_ids).batch_remove()

    return Success()


@api.route('/tasks/scanner/remove_scanner_tasks', methods=['POST'])
@permission_required('admin')
def scanner_remove_tasks():
    form = BatchRemoveScannerTaskForm().validate_for_api()
    task_ids = form.taskIds.data
    scanner.BatchRemoveTask(task_ids).batch_remove()
    return Success()


@api.route('/task_sessions/remove', methods=['POST'])
def task_sessions_remove():
    """移除task_session

    Returns:
        [type]: [description]
    """
    form = BatchRemoveTaskSessionForm().validate_for_api()
    tasks = Task.objects(
        jobId=form.jobId.data,
        taskSessionId__in=form.taskSessionIds.data).only('taskId')
    scanner.BatchRemoveTaskSession(tasks.distinct('taskSessionId')).batch_remove()
    tasks.delete()
    return Success()


@api.route('/tasks/stop', methods=['POST'])
def tasks_stop():
    """批量停止task, 当taskIds为空时, 停止job下所有的task

    Returns:
        [type]: [description]
    """
    form = BatchStopTaskForm().validate_for_api()
    if not form.taskIds.data:
        task_ids = form.job.get_task_ids(asset=form.asset)
    else:
        task_ids = form.job.get_task_ids(asset=form.asset) & set(form.taskIds.data)
    scanner.BatchStopTask(task_ids).batch_stop()
    return Success()


@api.route('/task_sessions/stop', methods=['POST'])
def task_sessions_stop():
    """停止task_session

    Returns:
        [type]: [description]
    """
    form = BatchStopTaskSessionForm().validate_for_api()
    tasks = Task.objects(
        jobId=form.jobId.data,
        taskSessionId__in=form.taskSessionIds.data).only('taskId')
    scanner.BatchStopTaskSession(tasks.distinct('taskSessionId')).batch_stop()
    tasks.update(status='stopped')
    return Success()


@api.route('/task/rescan', methods=['POST'])
def rescan():
    """重试任务, task级别

    Returns:
        [type]: [description]
    """
    form = RescanTaskForm().validate_for_api()
    if (task_id := form.taskId.data) and task_id in form.job.get_task_ids(asset=form.asset):
        scanner.Scheduler(task_id=task_id).rescan()
    return Success()


@api.route('/batch_import/<string:uid>', endpoint='admin_batch_import', methods=['POST'])
@permission_required('admin')
@api.route('/batch_import', endpoint='batch_import', methods=['POST'])
def batch_import(uid=''):
    """批量导入资产,支持本地excel导入/ 云防御资产导入
    """
    uid = uid if uid else get_jwt_identity()
    if not (operator := request.args.get('operator', '')):
        operator = g.user.username

    def allowed_file(filename):
        return '.' in filename and filename.rsplit('.', 1)[1].lower() in ['xlsx']

    body_assets = []
    asset_type = "web"
    if body := request.json:
        if isinstance(body, dict):
            asset_type = body.get('asset_type', 'web')
            body_assets = body.get("assets", [])
        elif isinstance(body, list):
            body_assets = body
    else:
        asset_type = request.form.get('asset_type', 'web')
    if asset_type and asset_type not in ('web', 'host'):
        raise UploadFileError(msg='asset_type 参数错误。')

    if file := request.files.get('file'):
        import_type = 'local'
        if not allowed_file(file.filename):
            raise UploadFileError(msg='文件格式错误。')
        f = BytesIO(file.read())
        if not (assets := Excel(f, 'Sheet1').data()):
            raise EmptyData(msg='内容为空。')
        if len(assets) > 500:
            raise ParameterError(msg='一次最多上传500条记录。')
    elif body:
        import_type = 'cloud'
        assets = body_assets
    else:
        raise EmptyData(msg='内容为空。')
    if not assets:
        raise EmptyData(msg='内容为空。')

    user = User.objects.filter(id=uid).first()
    if not user:
        raise ParameterError(msg='用户信息错误。')
    if import_type == "cloud":
        cloud_assets = {
            f"{_.get('protocol')}://{_.get('full_domain')} {_['point']}" for _ in job_util.get_cloud_assets(user)}
        _cloud_assets = {f"{_['target']} {_['sourceIp']}" for _ in assets}
        if error_asset := _cloud_assets - cloud_assets:
            raise EmptyData(msg=f'{list(error_asset)} 不是云防御资产')
    query_dict = {"uid": ObjectId(uid), "status": "doing", "operator": operator}
    if asset_type == 'host':
        query_dict['assetType'] = "host"
    else:
        query_dict['assetType'] = {"$ne": "host"}

    if exist_import := BatchImportAsset.objects.find(query_dict).first():
        if exist_import.current_index + 1 == exist_import.total:
            exist_import.status = 'done'
            exist_import.save(validate=False)
        else:
            raise ExistBatchImportAsset()

    _assets = []
    index = 0
    for asset in assets:
        if asset_type == 'host':
            asset['sourceIp'] = asset.get('target')
        _assets.append(Asset(
            name=asset.get('name'),
            target=asset.get('target'),
            sourceIp=asset.get('sourceIp'),
            domain=asset.get('domain'),
            index=index))
        index += 1
    batch_import_asset = BatchImportAsset(
        uid=ObjectId(uid), assets=_assets, total=len(_assets),
        type=import_type, operator=operator, assetType=asset_type)
    batch_import_asset.save(validate=False)
    log_dict = {
        'log_type': g.log_type,
        'logSource': g.logSource,
        'operator': g.operator,
        'ip': g.ip,
        'uid': g.audit_uid.__str__()
    }
    func = do_batch_import_asset
    func = func if DEBUG else func.delay
    func(str(batch_import_asset.pk), **log_dict)
    return Success()


@api.route('/batch_status/<string:uid>', endpoint='admin_batch_status', methods=['GET'])
@permission_required('admin')
@api.route('/batch_status', endpoint='batch_status', methods=['GET'])
def batch_status(uid=''):
    uid = uid if uid else get_jwt_identity()
    if not (operator := request.args.get('operator', '')):
        operator = g.user.username

    query_dict = {"uid": ObjectId(uid), "status": "doing", "operator": operator}

    asset_type = request.args.get('asset_type', '')
    if asset_type == 'host':
        query_dict['assetType'] = "host"
    else:
        query_dict['assetType'] = {"$ne": "host"}

    if exist_import := BatchImportAsset.objects.find(query_dict).first():
        progress = round((exist_import.current_index + 1) / exist_import.total * 100)

        progress = 100 if progress > 100 else progress
        if progress == 100 and request.args.get('clean', '') == '1':
            BatchImportAsset.objects(pk=exist_import.pk) \
                .update_one(status='done')

        if progress == 100:
            return jsonify(
                code=200,
                msg='ok',
                progress=progress,
                assets=exist_import.assets,
                type=exist_import.type)
        else:
            return jsonify(
                code=200,
                msg='ok',
                progress=progress,
                type=exist_import.type)
    else:
        return Success()


@api.route('/tasks/config/update', methods=['POST'])
@permission_required('admin')
def tasks_config_update():
    """
    刷新任务在引擎端配置
    """
    form_obj = UpdateTaskGroupConfigForm().validate_for_api()
    for t in form_obj.asset_tasks:
        enable_task_group(t, insert=False)
    return Success()


@api.route('/cloud_assets/<string:uid>', endpoint='admin_cloud_assets', methods=['GET'])
@permission_required('admin')
@api.route('/cloud_assets', endpoint='cloud_assets', methods=['GET'])
def cloud_assets_status(uid=''):
    if uid:
        user = User.objects.with_id(uid)
    else:
        user = g.user
    cloud_assets = job_util.get_cloud_assets(user)
    targets = []
    for asset in cloud_assets:
        asset['targetUrl'] = f"{asset.get('protocol')}://{asset.get('full_domain')}"
        targets.append(asset['targetUrl'])

    exists_targets = [i.targetUrl for i in Job.objects.only('targetUrl').filter(uid=user.pk, targetUrl__in=targets)]

    _assets = []
    for asset in cloud_assets:
        if (targetUrl := asset['targetUrl']) in exists_targets:
            continue
        name = asset.get('name') if asset.get('name') else '未命名'
        _assets.append({
            "name": name,
            "target": targetUrl,
            "sourceIp": asset.get('point'),
            "domain": asset.get('domain')
        })
    return jsonify(
        code=200,
        msg='ok',
        imported_count=len(cloud_assets) - len(_assets),
        assets=_assets
    )


@api.route('/verifications', endpoint='job_certifications', methods=['POST'])
@permission_required('admin')
def verifications():
    """
    资产认证, 支持批量认证
    """
    form = JobBatchVerification().validate_for_api()
    assets = []
    for job_id in form.job_ids.data:
        verify_obj = JobVerifyHelper(job_id=job_id)
        # 不存在就跳过
        if not (job := verify_obj.job):
            continue
        if verify_obj.admin_asset_verify(form.is_verified.data, note=form.note.data):
            assets.append(job.targetUrl)
            if form.is_verified.data and form.enable_task.data:
                switch_job_tasks(job, True)
    UJobLog().info(
        '批量审核', detail=f"批量审核{'、'.join(assets[:3])}等{len(assets)}个资产为{'已认证' if form.is_verified.data else '未认证'}")
    return Success()


@api.route('/cyd_enabled', endpoint='job_cyd_enabled', methods=['POST'])
def cyd_enabled():
    """查询资产在云防御的状态, 并更新数据
    """
    form = JobCydEnableForm().validate_for_api()
    if g.entity_type == 'user' and form.uid.data != g.user.pk.__str__():
        raise PermissionDenied()

    job = Job.objects.get_or_404(pk=form.job_id.data)
    user = User.objects.get_or_404(pk=form.uid.data)

    oms_client = Client()
    resp = oms_client.get('/api/inner/asset_status/', params={
        'username': user.username,
        'asset': job.targetUrl
    }).get('data', False)

    if job.cydEnabled != resp and isinstance(resp, bool):
        Job.objects(pk=job.pk).update_one(cydEnabled=resp)
    return jsonify(cyd_enabled=resp)


@api.route('/get_next_exe_time', methods=['GET'])
def get_next_exe_time():
    request_args = request.args
    task_id = request_args.get('task_id')
    query = {'pk': task_id}

    if g.role.name == '普通用户':
        query['uid'] = g.user.id
    task = Task.objects.filter(**query).first()
    res = scanner.ScannerRequest().get(f'/v1/tasks:get?task_id={task.taskId}')

    data = {}
    if res and res.get('code') != 5:
        data['nextExecDate'] = res.get('next_start_at')

    return jsonify({'code': 200, 'message': 'ok', 'results': data})


@api.route('/get', methods=['GET'])
def get_jobs():
    utility_key = ('enableAlert', 'enableMonitor', 'ignoredWarnIdMap', 'note', 'targetUrl')
    allow_args = ('isVerified', 'note', 'target', 'page', 'limit')
    task_type_allow = ('vul', 'ssl', 'securityEvent', 'http', 'ping', 'asset', 'sitePortrait', 'content')

    request_args = get_request_args(allow_args)

    if g.role.name == '普通用户':
        uid = g.user.id
        use_user_info = False
        query = {}
    else:
        uid = None
        use_user_info = True

        # Get condition of uid.
        user_query = get_request_args(('plan_name', 'username'))
        if plan_name := user_query.get('plan_name'):
            plan_id_list = [plan.id for plan in Packages.objects.filter(name__contains=plan_name)]
            uid_set_balance = {balances.uid for balances in Balances.objects.find({'planId': {'$in': plan_id_list}})}
        else:
            uid_set_balance = {}
        if username := user_query.get('username'):
            uid_set_user = {user.id for user in User.objects.filter(username__contains=username)}
        else:
            uid_set_user = {}
        if uid_set_balance and uid_set_user:
            query = {'uid': {'$in': tuple(uid_set_user.intersection(uid_set_balance))}}
        elif not uid_set_user and uid_set_balance:
            query = {'uid': {'$in': tuple(uid_set_balance)}}
        elif not uid_set_balance and uid_set_user:
            query = {'uid': {'$in': tuple(uid_set_user)}}
        else:
            query = {}

    last_task = LastTask(
        uid=uid,
        task_type_list=task_type_allow,
        request_args=request_args,
        job_query=query,
        use_paginate=True
    )
    data = last_task.get()

    job_dict = last_task.job_dict
    new_job_dict = {}
    uid_list = []
    for k, v in job_dict.items():
        # special key
        new_job_dict[k] = {'_id': str(k), 'categoryStatus': {}}

        # utility key
        for _k in utility_key:
            new_job_dict[k][_k] = getattr(v, _k, '')

        # add uid
        uid_list.append(v.uid)

    if use_user_info:
        user_dict = {user['_id']: user for user in UserHelper({'uids': uid_list}).get_data()}

    for k, v in data.items():
        for item in v:
            _job_id = item.get('_id')
            _job = new_job_dict.get(_job_id)

            category_status = _job['categoryStatus']
            _settings = getattr(_job, f'{k}Settings', '')
            _update_time = item.get('endTime')
            _warnings = item.get('warnings') if item.get('warnings') else []
            _w_count = 0
            for _w in _warnings:
                if SumWarnings.is_warning_resolved(_w):
                    continue
                _w_count += 1

            category_status[k] = {
                'enable': _settings.enable if _settings else False,
                'status': 'safe',
                'updateTime': arrow.get(_update_time).isoformat() if _update_time else '',
                'warningCount': _w_count
            }

    new_data = list(new_job_dict.values())

    results = {
        'count': last_task.job_count,
        'data': new_data,
        'page': int(request_args.get('page', 1))
    }

    return jsonify({'code': 200, 'msg': 'ok', 'results': results})


@api.route('/setting/get')
def get_jobs_settings():
    utility_key = ('enableAlert', 'cydEnabled', 'enableMonitor',
                   'note', 'ignoredWarnIdMap', 'targetUrl', 'sourceIp'
                                                            'alertSettings', 'httpSettings', 'pingSettings',
                   'assetSettings',
                   'securityEventSettings', 'sslSettings', 'vulSettings')

    allow_args = ('job_id',)

    request_args = get_request_args(allow_args)

    if g.role.name == '普通用户':
        uid = g.user.id
    else:
        uid = None

    job_helper = JobHelper(request_args, uid, use_asset=True)
    if not (job := tuple(job_helper.get_data())):
        results = {}
    else:
        job = job[0]

        new_job = {'id': str(job.id), 'uid': str(job.uid), 'assetId': str(job.assetId)}
        for k in utility_key:
            new_job[k] = getattr(job, k, '')

        results = {'job': new_job}
    return jsonify({'code': 200, 'msg': 'ok', 'results': results})


@api.route('/setting/last_task/get')
def get_last_task_settings():
    results = {
        'alertSettings': {},
        'taskSettings': {}
    }
    allow_args = ('job_id',)
    task_type_tuple = [item for item in TASK_TYPE_TUPLE if item not in (TaskType.http.value, TaskType.ping.value)]

    request_args = get_request_args(allow_args)
    job_id = request_args.get('job_id')

    task_match = {'jobId': ObjectId(job_id), 'triggerType': 'manual'}
    if g.role.name == '普通用户':
        task_match['uid'] = g.user.id

    if task := Task.objects.filter(**task_match).order_by('-id').first():
        results['alertSettings'] = task.alertSettings

    # TODO: 临时解决方案
    pipeline_dict = {}
    for task_type in task_type_tuple:
        task_match['taskType'] = task_type
        pipeline_dict[task_type] = [
            {'$match': task_match.copy()},
            {'$sort': {'endTime': -1}},
            {'$limit': 1},
            {'$project': {'taskSettings': 1}}
        ]

    data = {}
    for task_type, pipeline in pipeline_dict.items():
        data[task_type] = tuple(Task.objects.aggregate(pipeline))

    for task_type in task_type_tuple:
        if _temp_data := data.get(task_type):
            results['taskSettings'][task_type] = _temp_data[0]['taskSettings']
        else:
            results['taskSettings'][task_type] = {}

    return jsonify({'code': 200, 'msg': 'ok', 'results': results})


@api.route('/job_download', endpoint='job_download', methods=['post'])
@permission_required('admin')
def job_download():
    """
    查询需要下载的资产信息
    """
    form = JobDownloadForm().validate_for_api()
    query_dict = form.data.get('filter', {})
    jobs = Job.objects.find(query_dict).order_by('-id')
    data = JobDownload().get(jobs)
    return jsonify(
        code=200,
        msg='ok',
        data=data,
    )


def save_scan_tasks(job_id_list: list):
    for job in JobHelper({}, job_query={'_id': {'$in': job_id_list}}, use_asset=True).get_data():
        for task_type in MONITOR_TYPE_TUPLE:

            settings = getattr(job, f"{task_type}Settings")

            schedule = SCHEDULE_MAP[task_type]

            if hasattr(settings, 'to_dict'):
                settings = settings.to_dict()
            else:
                settings = [item.to_dict() for item in settings]

            data = {
                "immediateExec": False,
                "jobId": job.id,
                "sourceIp": job.sourceIp,
                "enableMonitor": job.enableMonitor,
                f"{task_type}Settings": settings
            }

            schedule(data)

    return 0


def distribute_vul(job_id, job, enable_monitor, custom_monitor_page, pre_login):
    # 下发Web漏洞监测 vul
    data = {
        'immediateExec': False,
        'jobId': job_id,
        'sourceIp': job.sourceIp,
        'enableMonitor': enable_monitor,
        'vulSettings': job.vulSettings.to_dict(),
        'customMonitorPage': custom_monitor_page,
    }
    if pre_login:
        data['prelogin'] = pre_login
    vul(data=data)


def distribute_ssl(job_id, job, enable_monitor, custom_monitor_page):
    # 下发SSL安全监测 ssl
    data = {
        'immediateExec': False,
        'jobId': job_id,
        'sourceIp': job.sourceIp,
        'enableMonitor': enable_monitor,
        'sslSettings': job.sslSettings.to_dict(),
        'customMonitorPage': custom_monitor_page,
    }
    ssl(data=data)


def distribute_security_event(job_id, job, enable_monitor, custom_monitor_page, pre_login):
    # 下发安全事件监测 security_event
    data = {
        'immediateExec': False,
        'jobId': job_id,
        'sourceIp': job.sourceIp,
        'enableMonitor': enable_monitor,
        'securityEventSettings': job.securityEventSettings.to_dict(),
        'customMonitorPage': custom_monitor_page,
    }
    if pre_login:
        data['prelogin'] = pre_login
    security_event(data=data)


def distribute_content(job_id, job, enable_monitor, custom_monitor_page, pre_login):
    # 下发内容合格监测 content
    data = {
        'immediateExec': False,
        'jobId': job_id,
        'sourceIp': job.sourceIp,
        'enableMonitor': enable_monitor,
        'contentSettings': job.contentSettings.to_dict(),
        'customMonitorPage': custom_monitor_page,
    }
    if pre_login:
        data['prelogin'] = pre_login
    content(data)


def distribute_http(job_id, job, enable_monitor, custom_monitor_page):
    # 下发HTTP监测 http
    http_settings = []
    for item in job.httpSettings:
        http_settings.append(item.to_dict())
    data = {
        'immediateExec': False,
        'jobId': job_id,
        'sourceIp': job.sourceIp,
        'enableMonitor': enable_monitor,
        'httpSettings': http_settings,
        'customMonitorPage': custom_monitor_page,
    }
    http(data=data)


def distribute_ping(job_id, job, enable_monitor, custom_monitor_page):
    # 下发Ping监测 ping
    ping_settings = []
    for item in job.pingSettings:
        ping_settings.append(item.to_dict())
    data = {
        'immediateExec': False,
        'jobId': job_id,
        'sourceIp': job.sourceIp,
        'enableMonitor': enable_monitor,
        'pingSettings': ping_settings,
        'customMonitorPage': custom_monitor_page,
    }
    ping(data)


def distribute_asset(job_id, job, enable_monitor, asset):
    # 下发资产变动监测 asset
    data = {
        'jobId': job_id,
        'sourceIp': job.sourceIp,
        'enableMonitor': enable_monitor,
        'taskSettings': asset.taskSettings.to_dict(),
    }
    asset_schedule(data=data)


def distribute_monitor_config(asset, job, enable_monitor=True):
    # 下发配置任务到引擎
    job_id = str(job.id)

    custom_monitor_page = {'includeUrl': [], 'excludeUrl': []}
    if hasattr(job, 'customMonitorPage'):
        custom_monitor_page = job.customMonitorPage.to_dict()

    pre_login = dict()
    if hasattr(job, 'prelogin'):
        pre_login = job.prelogin.to_dict()

    # 下发Web漏洞监测 vul
    distribute_vul(job_id, job, enable_monitor, custom_monitor_page, pre_login)

    # 下发SSL安全监测 ssl
    distribute_ssl(job_id, job, enable_monitor, custom_monitor_page)

    # 下发安全事件监测 security_event
    distribute_security_event(job_id, job, enable_monitor, custom_monitor_page, pre_login)

    # 下发内容合格监测 content
    distribute_content(job_id, job, enable_monitor, custom_monitor_page, pre_login)

    # 下发HTTP监测 http
    distribute_http(job_id, job, enable_monitor, custom_monitor_page)

    # 下发Ping监测 ping
    distribute_ping(job_id, job, enable_monitor, custom_monitor_page)

    # 下发资产变动监测 asset
    distribute_asset(job_id, job, enable_monitor, asset)


def batch_distribute_monitor_config(jobs, user_package_maps, user_balance_maps, user_maps, asset_maps, balance_maps,
                                    action, enable_monitor=False):
    job_ids = []
    success_job_list = []

    for job in jobs:
        uid = job.uid
        asset_id = job.assetId
        package_id = user_package_maps.get(uid, '')
        balance_id = user_balance_maps.get(uid, '')
        if not package_id or not balance_id:
            continue

        asset = asset_maps.get(asset_id)
        if asset is None:
            continue

        # 套餐和套餐使用情况
        balance = balance_maps.get(balance_id)
        if balance is None:
            continue
        if action == 'monitor' and enable_monitor:
            if not check_job_balance(balance.balance):
                continue

        g.entity_type = 'user'
        g.user = user_maps[uid]
        g.role = user_maps[uid].roleIds[0]

        if action == 'alert':
            enable_monitor = job.enableMonitor

        check_job_settings_interval(balance.balance, job, asset)
        try:
            distribute_monitor_config(asset, job, enable_monitor)
        except Exception:
            continue

        if action == 'monitor':
            domain_enable_used_inc = check_site_domain_enable_used(enable_monitor)
            http_enable_used_inc = check_http_max_task_enable_used(job.httpSettings, enable_monitor)
            ping_enable_used_inc = check_ping_max_task_enable_used(job.pingSettings, enable_monitor)
            site_port_trait_used_inc = check_site_port_trait_used(job.vulSettings, enable_monitor)
            balance.balance.domainEnable.used += domain_enable_used_inc

            Balances.objects(pk=balance.id).update_one(inc__balance__domainEnable__used=domain_enable_used_inc,
                                                       inc__balance__httpMaxTaskEnable__used=http_enable_used_inc,
                                                       inc__balance__pingMaxTaskEnable__used=ping_enable_used_inc,
                                                       inc__balance__sitePortrait__used=site_port_trait_used_inc)
        success_job_list.append(job.targetUrl)
        job_ids.append(job.id)

    return success_job_list, job_ids


@api.route('/batch_switch_monitor', endpoint='batch_switch_monitor', methods=['post'])
@permission_required('admin')
def batch_switch_monitor():
    """
    批量开启/关闭监测开关
    """

    form = JobBatchMonitorOrAlertSwitchForm().validate_for_api()
    switch = form.data.get('switch', False)
    user_ids = form.user_ids
    job_ids = form.job_ids
    message = ""

    # 查询用户
    package_ids, user_ids, user_maps, user_package_maps, balance_ids, user_balance_maps = get_active_user_info(user_ids)
    if not user_ids or not package_ids or not balance_ids:
        message = "用户不存在或者没有开通套餐"
        raise ServerError(msg=message)

    # 根据用户筛选资产
    if switch:
        _enable_monitor = False
    else:
        _enable_monitor = True
    jobs = get_verified_job_list(user_ids, job_ids, enable_monitor=_enable_monitor)
    if not jobs:
        message = "资产未认证或已经在目标状态"
        raise ServerError(msg=message)

    # 查询用户套餐使用情况
    balance_maps = get_balance_maps(balance_ids, switch)
    if not balance_maps:
        message = "没有开通套餐或者套餐已过期"
        raise ServerError(msg=message)

    if not message:
        asset_ids = [job.assetId for job in jobs]
        asset_maps = get_asset_maps(asset_ids)
        success_job_list, job_ids = batch_distribute_monitor_config(jobs, user_package_maps, user_balance_maps,
                                                                    user_maps, asset_maps, balance_maps,
                                                                    action="monitor", enable_monitor=switch)

        if not success_job_list:
            raise ServerError(msg="已超出资产数量限制")

        message = success_job_list
        Job.objects(pk__in=job_ids).update(enableMonitor=switch)

    return jsonify(code=200, msg='ok', data=message)


@api.route('/batch_switch_alert', endpoint='batch_switch_alert', methods=['post'])
@permission_required('admin')
def batch_switch_alert():
    """
    批量开启/关闭告警开关
    """

    form = JobBatchMonitorOrAlertSwitchForm().validate_for_api()
    switch = form.data.get('switch', False)
    user_ids = form.user_ids
    job_ids = form.job_ids
    message = ""

    # 查询用户
    package_ids, user_ids, user_maps, user_package_maps, balance_ids, user_balance_maps = get_active_user_info(user_ids)
    if not user_ids or not package_ids or not balance_ids:
        message = "用户不存在或者没有开通套餐"
        raise ServerError(msg=message)

    # 根据用户筛选资产
    if switch:
        _enable_alert = False
    else:
        _enable_alert = True
    jobs = get_verified_job_list(user_ids, job_ids, enable_alert=_enable_alert)
    if not jobs:
        message = "资产未认证或已经在目标状态"
        raise ServerError(msg=message)

    # 查询用户套餐使用情况
    balance_maps = get_balance_maps(balance_ids, switch)
    if not balance_maps:
        message = "没有开通套餐或者套餐已过期"
        raise ServerError(msg=message)

    if not message:
        asset_ids = [job.assetId for job in jobs]
        asset_maps = get_asset_maps(asset_ids)
        success_job_list, job_ids = batch_distribute_monitor_config(jobs, user_package_maps, user_balance_maps,
                                                                    user_maps, asset_maps, balance_maps,
                                                                    action="alert", enable_monitor=False)

        if not success_job_list:
            raise ServerError(msg="已超出资产数量限制")

        message = success_job_list
        Job.objects(pk__in=job_ids).update(enableAlert=switch)

    return jsonify(code=200, msg='ok', data=message)


@api.route('/admin_enable_proxy_ip', endpoint='admin_enable_proxy_ip', methods=['POST'])
@permission_required('admin')
def admin_enable_proxy_ip():
    form = EnableProxyIpForm().validate_for_api()
    job_ids = form.job_ids
    enable_proxy_ip = form.enable_proxy_ip.data
    jobs = Job.objects.filter(id__in=job_ids, assetType__ne="host")
    if not jobs:
        raise ParameterError(msg="选择的web资产不存在")
    jobs.update(enable_proxy_ip=enable_proxy_ip)
    batch_update_task_config.delay(job_ids=[str(j.id) for j in jobs])
    affects = [j.targetUrl for j in jobs]
    log_info = {
        'affects': affects,
        'actionMessage': '资产管理',
        'detail': f'{"开启" if enable_proxy_ip else "关闭"}IP代理: 资产地址: {",".join(affects)}'
    }
    UJobLog(**log_info).info(log_info['actionMessage'], log_info['detail'])
    return jsonify(code=200, msg='ok', data={})


@api.route('/get_all_proxy_ips', endpoint='get_all_proxy_ips', methods=['GET'])
@permission_required('admin')
def get_all_proxy_ips():
    data = GetAllProxyIPs().get()
    return jsonify(code=200, msg='ok', data=data)
